package xyz.lwm.lazycat.beans;

import xyz.lwm.lazycat.annotation.Bean;
import xyz.lwm.lazycat.annotation.Conditional;
import xyz.lwm.lazycat.annotation.Inject;
import xyz.lwm.lazycat.annotation.Order;
import xyz.lwm.lazycat.annotation.Value;
import xyz.lwm.lazycat.utility.Assert;
import xyz.lwm.lazycat.utility.CollectionUtil;
import xyz.lwm.lazycat.utility.It;
import xyz.lwm.lazycat.utility.StringUtil;
import xyz.lwm.lazycat.utility.reflect.AnnotationUtil;
import xyz.lwm.lazycat.utility.reflect.Annotations;
import xyz.lwm.lazycat.utility.reflect.ReflectUtil;

import javax.annotation.Resource;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * bean design builder
 *
 * @author lwm
 * @param <T>
 */
public final class BeanDesignBuilder<T> {

    private final BeanDesignImpl<T> design = new BeanDesignImpl<>();

    /**
     * 设置 bean 名称, 一般可以自动生成 name, 根据注解、类名、方法名生成 name; <br>
     * 但如果使用 {@link BeanDesignBuilder#instantiable(Instantiable, Dependency...)} 则需要手动设置 name
     *
     * @param name bean 名称
     * @return BeanDesignBuilder
     */
    public BeanDesignBuilder<T> name(String name) {
        design.setName(name);
        return this;
    }

    /**
     * 设置 bean 类型, 并自动获取注解, 必须设置
     *
     * @param type bean 类型
     * @return BeanDesignBuilder
     */
    public BeanDesignBuilder<T> type(Class<T> type) {
        design.setType(type);
        addAnnotations(type);
        return this;
    }

    /**
     * 创建 instantiable, 并自动设置 dependsOn 依赖
     *
     * @param constructor bean 构造器
     */
    public BeanDesignBuilder<T> instantiable(Constructor<?> constructor) {
        design.setInstantiable(new InstantiableWithConstructor(constructor));
        setDependsOn(constructor.getParameters(), constructor.getDeclaringClass());
        return this;
    }

    /**
     * 创建 instantiable, 并自动设置 dependsOn 依赖
     *
     * @param method 创建 bean 的方法
     * @param targetDependency 目标对象的相关信息
     */
    public BeanDesignBuilder<T> instantiable(Method method, Dependency targetDependency) {
        design.setInstantiable(new InstantiableWithMethod(method, targetDependency));
        addAnnotations(method);
        setDependsOn(method.getParameters(), method.getDeclaringClass());
        return this;
    }

    /**
     * 直接设置 instantiable 和 dependencies, 通常用于手动注入 bean
     *
     * @param instantiable instantiable
     * @param dependencies dependencies
     */
    public BeanDesignBuilder<T> instantiable(Instantiable instantiable, Dependency... dependencies) {
        design.setInstantiable(instantiable);
        if (CollectionUtil.isNotEmpty(dependencies)) {
            design.setDependsOn(dependencies);
        }
        return this;
    }

    private void addAnnotations(AnnotatedElement element) {
        if (design.getAnnotations() == null) {
            design.setAnnotations(AnnotationUtil.getAnnotations(element));
        } else {
            design.getAnnotations().addAnnotations(element);
        }
    }

    private void setDependsOn(Parameter[] parameters, Class<?> owner) {
        Dependency[] dependencies = new Dependency[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Annotations paramAnn = AnnotationUtil.getAnnotations(parameter);
            String beanName = It.of(paramAnn.getAnnotation(Inject.class))
                    .let(Inject::value).getOr(null);
            dependencies[i] = new Dependency(owner, beanName, parameter.getParameterizedType());
        }
        design.setDependsOn(dependencies);
    }

    public BeanDesign<T> build() {
        check();
        initBeanName();
        initOrder();
        initInjections();
        initConditions();
        afterPropertiesInit();
        return design;
    }

    private void check() {
        Assert.notNull(design.getType(), "bean type");
        Assert.notNull(design.getInstantiable(), "executable");
    }

    /**
     * 初始 bean name. 如未指定, 则从 Bean 注解中获取, bean 注解没有则使用类名首字母小写或方法名
     */
    private void initBeanName() {
        if (StringUtil.isBlank(design.getName())) {
            Annotations annotations = design.getAnnotations();
            Instantiable instantiable = design.getInstantiable();
            if (instantiable instanceof InstantiableWithMethod) {
                It.of(annotations.getAnnotation(Bean.class))
                        .let(Bean::value).filter(StringUtil::isNotBlank).orElse(() -> {
                            InstantiableWithMethod inst =
                                    (InstantiableWithMethod) instantiable;
                            return inst.getMethod().getName();
                        }).also(design::setName);

            } else if (instantiable instanceof InstantiableWithConstructor) {
                It.of(annotations.getAnnotation(Bean.class))
                        .let(Bean::value).filter(StringUtil::isNotBlank).orElse(() -> {
                            String simpleName = design.getType().getSimpleName();
                            return StringUtil.lowercaseChar(simpleName, 0);
                        }).also(design::setName);

            }
        }
        Assert.isTrue(StringUtil.isNotBlank(design.getName()), "bean name cannot be blank");
    }

    /**
     * 获取 Order 注解的值, 并设置到 design.order 中, 默认值是 Indexes.DEFAULT
     */
    private void initOrder() {
        int order = It.of(design.getAnnotations())
                .let(a -> a.getAnnotation(Order.class))
                .let(Order::value).getOr(Indexes.DEFAULT);
        design.setOrder(order);
    }

    /**
     * 遍历字段，获取所有需要注入的字段, 并添加到design.injections中
     */
    private void initInjections() {
        List<Injection> injections = new ArrayList<>();
        ReflectUtil.findDeclaredFields(design.getType()).values().forEach(field -> {
            Annotations annotations = AnnotationUtil.getAnnotations(field);
            if (annotations.hasAnnotation(Inject.class) || annotations.hasAnnotation(Resource.class)) {
                It<String> beanName = It.of(annotations.getAnnotation(Inject.class)).let(Inject::value);
                if (!beanName.isPresent()) {
                    beanName = It.of(annotations.getAnnotation(Resource.class)).let(Resource::name);
                }
                injections.add(new Injection(design.getType(), beanName.getOr(null),
                        field.getGenericType(), field.getName()));
            } else if (annotations.hasAnnotation(Value.class)) {
                It.of(annotations.getAnnotation(Value.class)).let(Value::value)
                        .also(value -> injections.add(new ValueInjection(design.getType(),
                                null, field.getGenericType(), field.getName(), value)));
            }
        });
        design.setInjections(injections.toArray(new Injection[0]));
    }

    /**
     * 从 design.annotations 中获取 Conditional 注解, 并添加到 design.conditions 中
     */
    private void initConditions() {
        Conditional conditional = design.getAnnotations().getAnnotation(Conditional.class);
        if (conditional != null && CollectionUtil.isNotEmpty(conditional.value())) {
            Condition[] conditions = Arrays.stream(conditional.value())
                    .map(ReflectUtil::newInstance).toArray(Condition[]::new);
            design.setConditions(conditions);
        }
    }

    private void afterPropertiesInit() {
        if (design.getDependsOn() == null) {
            design.setDependsOn(new Dependency[0]);
        }
        if (design.getInjections() == null) {
            design.setInjections(new Injection[0]);
        }
        if (design.getConditions() == null) {
            design.setConditions(new Condition[0]);
        }
    }

}
